/*
 * Copyright 2020 Liu Jie <jieliu2000@live.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.beyondsoft.sabg.edu.configure.sercurity;


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import com.beyondsoft.sabg.edu.model.db.core.UserAccount;
import com.beyondsoft.sabg.edu.model.exception.GenericBusinessException;
import com.beyondsoft.sabg.edu.model.exception.LoginException;
import com.beyondsoft.sabg.edu.repository.core.SystemSettingRepo;
import com.beyondsoft.sabg.edu.service.core.UserAccountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


@Component
public class JwtTokenProvider {

    public static final String HEADER_AUTHORIZATION_KEY = "Authorization";
    public static final String HEADER_AUTHORIZATION_VALUE_PREFIX = "Bearer";
    public static final String KEY_USER_NAME = "username";

    private static String JWT_SECRET;

    private static JWTSigner JWT_SIGNER;

    private static final long TOKEN_INVALID_DURATION = 5 * 60 * 60 * 1000;
    private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
    private static final ThreadLocal<JWT> threadLocalJWT = new ThreadLocal<>();
    @Autowired
    private UserAccountService userAccountService;
    @Autowired
    private SystemSettingRepo systemSettingRepo;

    @PostConstruct
    public void init() {
//        systemSettingRepo.findByName("JWT_SECRET").ifPresentOrElse(
//                setting -> {
//                    JWT_SECRET = setting.getValue();
//                    JWT_SIGNER = JWTSignerUtil.hs256(JWT_SECRET.getBytes(StandardCharsets.UTF_8));
//                },
//                () -> {
//                    throw new GenericBusinessException("JWT_SECRET setting is absent");
//                }
//        );
    }

    public String generateToken(String username) {
        return generateToken(username, null);
    }

    public String generateToken(String username, Long duration) {
        if(StrUtil.isEmpty(username)){
            throw new LoginException("username is null");
        }
        Map<String, Object> payloadMap = new HashMap(2);
        payloadMap.put("username", username);
        long now = System.currentTimeMillis();
        if (duration == null) {
            payloadMap.put("exp", DateUtil.date(now + TOKEN_INVALID_DURATION));
        } else {
            payloadMap.put("exp", DateUtil.date(now + duration));
        }
        payloadMap.put("iat", DateUtil.date(now));
        payloadMap.put("nbf", DateUtil.date(now));
        return JWTUtil.createToken(payloadMap, JWT_SIGNER);
    }

    public Authentication getAuthentication(String token) {
        String userName = getUsername(token);
        if (StringUtils.isEmpty(userName)) {
            return null;
        }
        UserAccount account = userAccountService.findByUsername(userName);
        return new UsernamePasswordAuthenticationToken(account, "", account.getAuthorities());
    }

    public String getUsername(String token) {
        try {
            if(!verifyToken(token)){
                throw new LoginException("verify failed,token is invalid!");
            }
            return (String) threadLocalJWT.get()
                    .getPayloads()
                    .get(KEY_USER_NAME);
        } catch (Exception e) {
            throw new LoginException("getUsername from jwt token failed:" + e.getMessage());
        }finally {
            threadLocalJWT.remove();
        }
    }

    private boolean verifyToken(String token) {
        try {
            JWT jwt = JWTUtil.parseToken(token);
            // 校验token时间
            JWTValidator.of(token).validateDate(new Date(),5);
            threadLocalJWT.set(jwt);

            synchronized (JWT_SIGNER) {
                return JWTUtil.verify(token, JWT_SIGNER);
            }
        }catch (Exception e){
            throw new LoginException("verifyToken failed");
        }
    }

    public String resolveToken(String bearerToken) {
        if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(HEADER_AUTHORIZATION_VALUE_PREFIX + " ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}
